using System;

namespace LiJ.Common.Core.Utils
{
	/// <summary>
	///  唯一编码生成类
	/// </summary>
	public class NumGenerator
	{
		private long _sequence;

		private const long MaxSequence = 4095L;

		private const int SequenceBitLength = 12;

		private const long MaxWorkerId = 1023L;

		private const int WorkerLeftShift = 12;

		private const int WorkerIdBitLength = 10;

		private const int TimestampLeftShift = 22;

		private long _lastTimestamp;

		/// <summary>
		///  当前的工作id 最大值不能超过（2的11次方 - 1）
		/// </summary>
		public long WorkId
		{
			get;
		}

		/// <summary>
		/// 构造函数
		/// </summary>
		/// <param name="workId">当前的工作id 最大值不能超过（2的11次方 - 1）</param>
		public NumGenerator(long workId)
		{
			if (workId <= 1023 && workId >= 0)
			{
                WorkId = workId;
				return;
			}
			throw new ArgumentException("workId", string.Format("worker Id can't be greater than {0} or less than 0", workId));
		}

		/// <summary>
		///  生成下一个编号
		/// </summary>
		/// <returns></returns>
		public long NextNum()
		{
			return GetTimestampAndSetSeq() << 22 | WorkId << 12 | _sequence;
		}

		/// <summary>
		///  获取数字编号（排除机器位，直接是时间戳 + 自增序列）
		/// </summary>
		/// <returns></returns>
		internal long GetNumWithoutWorker()
		{
			return GetTimestampAndSetSeq() << 12 | _sequence;
		}

		private long GetTimestampAndSetSeq()
		{
			long num = NumUtil.TimeMilliNum();
            //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
            if (num < _lastTimestamp)
			{
				throw new ArgumentException(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds", _lastTimestamp - num));
			}
			if (_lastTimestamp == num)
			{
                _sequence = (_sequence + 1 & 0xFFF);
				if (_sequence == 0L)
				{
					num = WaitNextMillis();
				}
			}
			else
			{
                _sequence = 0L;
			}
            _lastTimestamp = num;
			return num;
		}

		/// <summary>
		///  当前毫秒内序列使用完，等待下一毫秒
		/// </summary>
		/// <returns></returns>
		protected long WaitNextMillis()
		{
			long num;
			do
			{
				num = NumUtil.TimeMilliNum();
			}
			while (num <= _lastTimestamp);
			return num;
		}
	}
}
